package com.moyun.common.mock;
// 推荐静态导入,可以使代码更简洁

import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.*;

import java.util.*;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.*;

/**
 * <h3>moyun-base</h3>
 * <p>练习Mockito</p>
 *
 * @author : ybx
 * @date : 2022-04-23 15:34:33
 **/

public class PracticeMockito {
    // 创建Mock对象
    public static void main(String[] args) {
        // 创建 Mock 对象
        List mockedList = mock(List.class);

        // 使用 Mock 对象
        mockedList.add("one");
        mockedList.clear();

        // 验证目标方法已被调用
        verify(mockedList).add("one");
        verify(mockedList).clear();

        /**
         * 一旦 Mock Object 被创建,它将会记录它自身方法的调用情况.之后可以通过 verify 方法对其进行验证.
         */
    }

    /**
     * Mockito 中的Stub 通过对 Mock对象进行 Stubbing (打测试桩) 来实现.
     */
    @Test
    public void practiceStubbing() {
        // 我们可以 Mock 具体的类型,不仅仅是接口
        LinkedList mockedList = mock(LinkedList.class);

        // 测试桩
        when(mockedList.get(0)).thenReturn("first");
        when(mockedList.get(1)).thenThrow(new RuntimeException());

        // 输出 first
        System.out.println(mockedList.get(0));

        // 调用该方法将抛出异常
        System.out.println(mockedList.get(1));

        // 因为 get(999) 没有打测试桩,因此输出 Null
        System.out.println(mockedList.get(999));

        // 验证 get(0) 被调用的次数
        System.out.println(verify(mockedList).get(0));

    }

    @Test
    public void whenNotUseMockAnnotation_thenCorrect() {
        List mockList = mock(ArrayList.class);

        mockList.add("one");
        verify(mockList).add("one");
        assertThat(mockList.size()).isEqualTo(0);

        when(mockList.size()).thenReturn(100);
        assertThat(mockList.size()).isEqualTo(100);
    }

    @Mock
    List<String> mockedList;

    @BeforeEach
    public void init() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void whenUseMockAnnotation_thenMockIsInjected() {
        mockedList.add("one");
        verify(mockedList).add("one");
        assertThat(mockedList.size()).isEqualTo(0);

        when(mockedList.size()).thenReturn(100);
        assertThat(mockedList.size()).isEqualTo(100);
    }

    /**
     * 通过 @Spy 的 Spy 同样可以使用 Stubbing 的操作设定方法的返回值,但如果没有对其 Stubbing,则最终方法会委托给原本的实例来执行
     **/

    @Spy
    List<String> spiedList = new ArrayList<String>();

    @Test
    public void whenUserSpyAnnotation_thenSpyIsInjectedCorrectly() {
        spiedList.add("one");
        spiedList.add("two");

        verify(spiedList).add("one");
        verify(spiedList).add("two");
        assertThat(spiedList.size()).isEqualTo(2);

        // stubbing
        doReturn(100).when(spiedList.size());

        assertThat(spiedList.stream().distinct()).isEqualTo(100);
    }

    /**
     * 通过 @Captor 用于帮助创建 ArgumentCaptor 对象. ArgumentCaptor 与 verify 方法配合使用,用于捕获方法参数.
     **/
    @Captor
    ArgumentCaptor argument;

    @Test
    public void argumentCaptorTest() {
        List list1 = mock(List.class);
        List list2 = mock(List.class);
        list1.add("Scott");
        list2.add("Brian");
        list2.add("Jim");

        verify(list1).add(argument.capture());
        assertThat(argument.getValue()).isEqualTo("Scott");

        verify(list2, times(2)).add(argument.capture());

        assertThat(argument.getValue()).isEqualTo("Jim");
        assertThat(argument.getAllValues().toArray()).isEqualTo(new Object[]{"Scott", "Brian", "Jim"});
    }


    /**
     * @InjectMocks 用于创建一个示例, 并且将使用了 @Mock 的字段注入其中.
     **/

    public class MyDictionary {
        Map<String, String> wordMap;

        public MyDictionary() {
            this.wordMap = new HashMap<String, String>();
        }

        public void add(final String word, final String meaning) {
            wordMap.put(word, meaning);
        }

        public String getMeaning(final String word) {
            return wordMap.get(word);
        }
    }


}
